/**
 * Copyright 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.solarchallenge.server.servlets.gwtservices;

import static com.google.solarchallenge.server.enums.AppengineQueues.EMAIL_QUEUE;
import static com.google.solarchallenge.server.enums.EmailCategory.FILE_UPLOADED;
import static com.google.solarchallenge.server.enums.EmailParameters.APPLICATION_ID;
import static com.google.solarchallenge.server.enums.EmailParameters.DOCUMENT_CATEGORY;
import static com.google.solarchallenge.server.enums.EmailParameters.DOCUMENT_STATUS;
import static com.google.solarchallenge.server.enums.EmailParameters.EMAIL_CATEGORY;
import static com.google.solarchallenge.server.enums.EmailParameters.ENCODED_APPLICANT_KEY;
import static com.google.solarchallenge.server.persistence.PersistenceProvider.getUserAccountPersistenceProvider;
import static com.google.solarchallenge.shared.ServletPaths.SEND_EMAIL;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.google.solarchallenge.client.rpc.ApplicantService;
import com.google.solarchallenge.server.domain.ApplicantDao;
import com.google.solarchallenge.server.domain.ApplicationDao;
import com.google.solarchallenge.server.domain.UserAccountDao;
import com.google.solarchallenge.server.persistence.PMF;
import com.google.solarchallenge.server.servlets.login.LoginHelper;
import com.google.solarchallenge.shared.ApplicantStatus;
import com.google.solarchallenge.shared.ApplicationDocumentCategory;
import com.google.solarchallenge.shared.ApplicationStatus;
import com.google.solarchallenge.shared.DocumentStatus;
import com.google.solarchallenge.shared.LoginCategory;
import com.google.solarchallenge.shared.ServletPaths;
import com.google.solarchallenge.shared.dtos.ApplicantDto;
import com.google.solarchallenge.shared.dtos.ApplicationDto;
import com.google.solarchallenge.shared.dtos.UserAccountDto;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

/**
 * Server implementation for GWT RPC {@link ApplicantService}
 *
 * @author Arjun Satyapal
 */
public class ApplicantServiceImpl extends RemoteServiceServlet implements
    ApplicantService {
  private static final Logger logger = Logger.getLogger(ApplicantServiceImpl.class.getName());

  @Override
  public void registerApplicant(UserAccountDto userAccountDto,
      ApplicantDto applicantDto) {

    ApplicantDao applicantDao =
        ApplicantDao.fromDto(applicantDto, true/* isCreate */);
    applicantDao.setApplicantStatus(ApplicantStatus.PENDING_AGREEMENT);

    UserAccountDao userAccountDao =
        UserAccountDao.fromDto(userAccountDto, true/* isCreate */);
    userAccountDao.setApplicantDao(applicantDao);

    UserAccountDao existingUser =
      getUserAccountPersistenceProvider().getUserAccountDaoByEmail(
          userAccountDao.getEmail());

    //TODO: Ideally this should throw exception as user exists.
    if (existingUser != null) {
      logger.info("User " + userAccountDao.getEmail()
          + " already exists. So skipping it.");
      return;
    }

    // TODO: There will be a workflow which will enable login for
    // approvers.
    if (userAccountDao.getLoginCategory() == LoginCategory.APPROVER) {
      userAccountDao.setIsLoginAllowed(false);
    } else {
      userAccountDao.setIsLoginAllowed(true);
    }

    // If email is one of the Admins, then it will not work for
    // Applicant Home. At present, you need to specify properly
    // that the email is for admin at the time of registration.
    // Ideally this needs a separate screen.

    PersistenceManager pm = PMF.getPersistenceManager();
    try {
      // TODO : Add check for duplicate user.
      JDOHelper.makeDirty(userAccountDao, "applicantDao");
      JDOHelper.makeDirty(userAccountDao.getApplicantDao(), "usAddress");
      pm.makePersistent(userAccountDao);
    } catch (Throwable caught) {
      // TODO : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }
  }

  @Override
  public ApplicantStatus getApplicantStatus(String userAccountKey) {
    Key userAccKey = KeyFactory.stringToKey(userAccountKey);
    PersistenceManager pm = PMF.getPersistenceManager();
    ApplicantDao detached = null;
    try {
      UserAccountDao dao = pm.getObjectById(UserAccountDao.class, userAccKey);
      detached = pm.detachCopy(dao.getApplicantDao());
    } catch (Throwable caught) {
      // TODO : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }

    if (detached == null) {
      // TODO : Better error handling.
      try {
        // TODO : Add error message in servlets and redirect to better
        // url.
        getThreadLocalResponse().sendRedirect(
            ServletPaths.INVALID_URL.getRelativePath());
      } catch (IOException e) {
        // TODO: handle exception properly.
        e.printStackTrace();
      }
    } else {
      return detached.getApplicantStatus();
    }
    return ApplicantStatus.COULD_NOT_BE_DETERMINED;
  }

  @Override
  public void updateApplicantAgreementStatus(String userAccountKey,
      boolean isAccepted) {
    Key userAccKey = KeyFactory.stringToKey(userAccountKey);
    PersistenceManager pm = PMF.getPersistenceManager();
    try {
      UserAccountDao userDao =
          pm.getObjectById(UserAccountDao.class, userAccKey);
      ApplicantDao applicantDao = userDao.getApplicantDao();

      if (applicantDao != null) {
        if (isAccepted) {
          applicantDao.setApplicantStatus(ApplicantStatus.VERIFIED);
        } else {
          applicantDao.setApplicantStatus(ApplicantStatus.AGREEMENT_DENIED);
        }
      } else {
        try {
          // TODO : Add error message in servlets and redirect to better
          // url.
          getThreadLocalResponse().sendRedirect(
              ServletPaths.INVALID_URL.getRelativePath());
        } catch (IOException e) {
          // TODO: Handle exception properly.
          e.printStackTrace();
        }
      }
    } catch (Throwable caught) {
      // TODO : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }
  }

  @Override
  public String newApplicationId(String applicationDetails) {
    UserAccountDao userAccountDao =
        LoginHelper.getLoggedInUser(this.getThreadLocalRequest().getSession());

    if (userAccountDao == null) {
      throw new IllegalStateException(
          "User is not logged in or not in session.");
    }

    PersistenceManager pm = PMF.getPersistenceManager();
    ApplicationDao detached = null;
    try {
      ApplicationDao applicationDao = new ApplicationDao();
      applicationDao.setApplicantKey(userAccountDao.getEncodedKey());
      applicationDao.setApplicationDetails(applicationDetails);
      applicationDao.setOverAllApplicationStatus(ApplicationStatus.NOT_SUBMITTED);
      pm.makePersistent(applicationDao);
      detached = pm.detachCopy(applicationDao);
    } catch (Throwable caught) {
      // TODO(arjuns) : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }

    return detached.getId();
  }

  @Override
  public void storeUrlWithApplication(ApplicationDocumentCategory docCategory,
      String applicationId, String urlToAdd) {
    // TODO : Validate that the user uploading the files is same as for
    // application.
    PersistenceManager pm = PMF.getPersistenceManager();
    try {
      ApplicationDao applicationDao =
          pm.getObjectById(ApplicationDao.class, Long.parseLong(applicationId));

      switch (docCategory) {
        case SITE_DIAGRAM:
          applicationDao.getSiteDiagramUrls().add(urlToAdd);
          applicationDao.setSiteDiagramStatus(DocumentStatus.PENDING_REVIEW);
          break;

        case ELECTRICAL_DIAGRAM:
          applicationDao.getElectricalDiagramUrls().add(urlToAdd);
          applicationDao.setElectricalDiagramStatus(DocumentStatus.PENDING_REVIEW);
          break;
        case DIAGRAM_NOTES:
          applicationDao.getDiagramNoteUrls().add(urlToAdd);
          applicationDao.setDiagramNoteStatus(DocumentStatus.PENDING_REVIEW);
          break;
        default:
          throw new IllegalArgumentException("Unknown type : " + docCategory);
      }
      applicationDao.updateOverallStatus();
      sendMailForReceivingFile(pm, applicationId, applicationDao.getEncodedApplicantKey(),
          docCategory, DocumentStatus.PENDING_REVIEW);
      pm.makePersistent(applicationDao);
    } catch (Throwable caught) {
      // TODO(arjuns) : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }
  }

  // This must be called from a transaction.
  private void sendMailForReceivingFile(PersistenceManager pm,
      String applicationId, String encodedApplicantKey,
      ApplicationDocumentCategory docCategory, DocumentStatus docStatus) {
    Queue queue = QueueFactory.getQueue(EMAIL_QUEUE.getQueueName());
    TaskOptions taskOpt = TaskOptions.Builder.withUrl(SEND_EMAIL.getRelativePath());

    taskOpt.param(EMAIL_CATEGORY.getPublicString(), FILE_UPLOADED.name());
    taskOpt.param(ENCODED_APPLICANT_KEY.getPublicString(), encodedApplicantKey);
    taskOpt.param(DOCUMENT_CATEGORY.getPublicString(), docCategory.name());
    taskOpt.param(DOCUMENT_STATUS.getPublicString(), docStatus.name());
    taskOpt.param(APPLICATION_ID.getPublicString(), applicationId);

    queue.add(taskOpt);
  }

  @Override
  public ApplicationDto getApplicationDetails(String applicationId) {
    PersistenceManager pm = PMF.getPersistenceManager();
    ApplicationDto dto = null;
    try {
      ApplicationDao applicationDao =
          pm.getObjectById(ApplicationDao.class, Long.parseLong(applicationId));

      // Reading specifically as they are not in default fetch group.
      applicationDao.getDiagramNoteUrls();
      applicationDao.getElectricalDiagramUrls();
      applicationDao.getSiteDiagramUrls();

      dto = applicationDao.toDto();
    } catch (Throwable caught) {
      // TODO : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }

    return dto;
  }

  @Override
  public void submitApplication(String applicationId) {
    // TODO(arjuns) : Validate user is same.
    PersistenceManager pm = PMF.getPersistenceManager();
    try {
      ApplicationDao applicationDao =
          pm.getObjectById(ApplicationDao.class, Long.parseLong(applicationId));
      applicationDao.setOverAllApplicationStatus(ApplicationStatus.SUBMITTED_FOR_REVIEW);
      pm.makePersistent(applicationDao);
    } catch (Throwable caught) {
      // TODO : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }
  }

  @Override
  public ArrayList<ApplicationDto> getApplicationsByStatus(
      ApplicationStatus overallApplicationStatus) {
    UserAccountDao userAccountDao =
        LoginHelper.getLoggedInUser(this.getThreadLocalRequest().getSession());
    PersistenceManager pm = PMF.getPersistenceManager();
    Query query = pm.newQuery(ApplicationDao.class);
    query.setFilter("applicantKey == :key && overallApplicationStatus "
        + "== :overallApplicationStatus");
    ArrayList<ApplicationDto> returnList = new ArrayList<ApplicationDto>();
    try {
      @SuppressWarnings("unchecked")
      List<ApplicationDao> list =
          (List<ApplicationDao>) query.execute(userAccountDao.getKey(),
              overallApplicationStatus);
      for (ApplicationDao currDao : list) {
        returnList.add(currDao.toDto());
      }
    } catch (Throwable caught) {
      // TODO : handle exception properly.
      caught.printStackTrace();
    } finally {
      pm.close();
    }

    return returnList;
  }
}
